perm filename BOOK.DOC[LSP,JRA] blob sn#258219 filedate 1977-01-13 generic text, type T, neo UTF8
                                                       CONTENTS     i
␈⊃
␈⊃
␈⊃                  T A B L E   O F   C O N T E N T S
␈⊃
␈⊃
␈⊃
␈⊃CHAPTER                                                          PAGE
␈⊃
␈⊃            .1    Review and Reflection   .  .  .  .  .  .  .  .  . 1
␈⊃
␈⊃
␈⊃   BIBLIOGRAPHY                                                    28
␈⊃
␈⊃
␈⊃
␈⊃   INDEX                                                           31
␈⊃.1                                        Review and Reflection     1
␈⊃
␈⊃
␈⊃                      .1  Review and Reflection
␈⊃
␈⊃
␈⊃                    "I think that it is important to maintain  a view
␈⊃                    of  the field  that helps  minimize  the distance
␈⊃                    between theoretical and practical work."
␈⊃
␈⊃                                                Saul Amarel, [Ama 72]
␈⊃
␈⊃By way  of review  we sketch  the basic  LISP evaluator  of Section :
␈⊃eval plus the additional artifacts for label and function.
␈⊃
␈⊃There are  two arguments to  eval: a form 1,  that is,  an expression
␈⊃which can be evaluated; and  an association list or symbol  table. If
␈⊃the form is a constant, return that form. If the form is  a variable,
␈⊃find the value  of the variable in  the current environment.   If the
␈⊃form is a conditional  expression, then evaluate it according  to the
␈⊃semantics of conditional expressions.
␈⊃
␈⊃The form might also be a functional argument. In this case evaluation
␈⊃consists of associating the current environment with the function and
␈⊃returning that  construct as  value; in  LISP this  is done  with the
␈⊃funarg  device.  Any  other form  seen by  eval is  assumed to  be an
␈⊃application,  that  is,  a  function  followed  by   arguments.   The
␈⊃arguments are evaluated from  left-to-right and the function  is then
␈⊃applied to these arguments.
␈⊃
␈⊃The  part  of the  evaluator  which handles  function  application is
␈⊃called apply.  apply takes  three arguments: a LISP function,  a list
␈⊃of evaluated arguments, and the current symbol table. If the function
␈⊃is one  of the five  LISP primitives then  the appropriate  action is
␈⊃carried out. If the function  is a λ-expression then bind  the formal
␈⊃parameters (the λ-variables) to the evaluated arguments  and evaluate
␈⊃the body of the function. The function might also be the result  of a
␈⊃functional argument binding; in  this case apply the function  to the
␈⊃arguments  and use  the saved  environment, rather  than  the current
␈⊃environment, to  search for non-local  bindings.  If we  are applying
␈⊃the label  operator, recalling  page , we  build a  funarg-triple and
␈⊃new environment such that the environment bound in the triple  is the
␈⊃new environment. We then apply the function to the arguments  in this
␈⊃new environment.
␈⊃
␈⊃If  the function  has a  name we  look up  that name  in  the current
␈⊃environment.  Currently  we expect that  value to be  a λ-expression,
␈⊃
␈⊃________________
␈⊃ 1  Throughout   this  section  we   will  say   "form",  "variable",
␈⊃"λ-expression", etc.  rather than "a representation of a" ... "form",
␈⊃"variable",  "λ-expression",  etc. No  confusion  should  result, but
␈⊃remember that we are speaking imprecisely.
␈⊃2                                                                  .1
␈⊃
␈⊃
␈⊃which  is  then  applied.  However,  since  function  names  are just
␈⊃variables, there is no reason that the value of a function name could
␈⊃not be a simple value, say an atom, and that value can be  applied to
␈⊃the arguments.  This process  can continue  recursively until  a true
␈⊃function-object  is uncovered.   Examination of  apply on  page  will
␈⊃show  that   apply[X; ((A B)) ; ((X . CAR) ... )]  will   be  handled
␈⊃correctly.  The natural extension of this idea is to allow a computed
␈⊃function. That is, if the function passed to apply is  not recognized
␈⊃as one of the preceding cases, then evaluate the  function-part until
␈⊃it is recognized. Thus we will allow such forms as:
␈⊃
␈⊃            ((CAR (QUOTE (CAR (A . B)))) (QUOTE (A . B)))
␈⊃The addition of computed functions modifies apply (page ) slightly
␈⊃
␈⊃apply <= λ[[fn;args;environ]     [iscar[fn] → car[arg1[args]];
␈⊃                         iscons[fn] → cons[arg1[args];arg2[args]];
␈⊃                                ...                ...
␈⊃                         islambda[fn] → eval[ body[fn];
␈⊃                                         pairlis[vars[fn];args;enviro
␈⊃                         t → apply[      eval[fn;environ];
␈⊃                                 args;
␈⊃                                 environ] ]]
␈⊃
␈⊃The subset of LISP which is captured by this evaluator is a stong and
␈⊃useful  language even  though  it lacks  several of  the  more common
␈⊃programming   language  features 2.   This  subset   is   called  the
␈⊃applicative subset of LISP, since its computational ability  is based
␈⊃on  the   mathematical  idea  of   function  applications.   We  have
␈⊃persistently referred to our LISP procedures as LISP  functions, even
␈⊃though we have seen some differences between the concept  of function
␈⊃in  mathematics and  the concepts  of procedure  in LISP.  It  is the
␈⊃mathematical idea of function which the procedures of  an applicative
␈⊃language are trying to capture.  Regardless of differences  in syntax
␈⊃and evaluation schemes, the dominant characteristic of an applicative
␈⊃language  is  that a  given  "function"  applied to  a  given  set of
␈⊃
␈⊃________________
␈⊃ 2  It  is  "strong",  both  practically  and  theoretically.  It  is
␈⊃sufficiently  powerful  to  be  "universal"  in  the  sense  that all
␈⊃computable  functions  are   representable  in  LISP.  In   fact  the
␈⊃eval-apply  pair  represents  a  "universal  function",   capable  of
␈⊃simulating the behavior of  any other computable function.  The usual
␈⊃arguments about  the halting  problem ([Rog 67]) and  related matters
␈⊃are easily expressed  and proved in  this LISP. Indeed,  the original
␈⊃motivation  for  introducing  the M-to-S  expression  mapping  was to
␈⊃express the  language constructs  in the data  domain. Only  then was
␈⊃"S-expression LISP"  used  as   the  programming  language.   It  was
␈⊃S. Russell  who  convinced  Mc Carthy  that  the  theoretical  device
␈⊃represented in  eval and  apply could  in fact  be programmed  on the
␈⊃IBM704.
␈⊃.1                                        Review and Reflection     3
␈⊃
␈⊃
␈⊃arguments  always produced  the same  result: either  the computation
␈⊃produces an error, or it  doesn't terminate, or it always  produces a
␈⊃specific  value.   The  applicative  subset  of  LISP  doesn't  quite
␈⊃succeed.  The treatement of free variables calls the environment into
␈⊃play.  So in LISP, we must add a phrase about "a  given environment",
␈⊃and  the  point  becomes  that the  given  computation  in  the given
␈⊃environment always has the same effect. That means we have no  way to
␈⊃destructively  change  the  environment.  Languages  which  have such
␈⊃ability are  said to have  side-effects and are  the business  of the
␈⊃next chapter.
␈⊃
␈⊃LISP was the first language  to exploit procedures as objects  of the
␈⊃language.   The  idea  has  been  generalized  substantially  in  the
␈⊃intervening years. A concise statement of the more  general principle
␈⊃appears in [Pop 68a].
␈⊃
␈⊃                    "...This  brings  us  to  the
␈⊃                    sbject  of  items.   Anything
␈⊃                    which can  be the value  of a
␈⊃                    variable  is  an   item.  All
␈⊃                    items       have      certain
␈⊃                    fundamental rights.
␈⊃                       1. All items can be the actual parameters of f
␈⊃                       2. All items can be returned as results of fun
␈⊃                       3. All items can be the subject of assignment 
␈⊃                       4.All items can be tested for equality.
␈⊃
␈⊃                    ..."
␈⊃
␈⊃LISP  performs  well  on  the  first  two  principles,  allowing LISP
␈⊃functions to be  the arguments as well  as the results  of functions.
␈⊃The  fourth  principle  is  more difficult;  that  is,  test  for the
␈⊃equality of two LISP functions.   In can be shown ([Rog 67])  that no
␈⊃algorithm can be constructed  which will perform such  test.  However
␈⊃in more selective settings, program equality can be established, both
␈⊃theoretically  ([Man 74]),   and  practically   ([Boy 75],  [Dar 73],
␈⊃[Car 76]).
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃________________
␈⊃ 3 This issue will be postponed until Chapter .
␈⊃4                                                                  .1
␈⊃
␈⊃
␈⊃
␈⊃The  language, POP-2,  has also  generalized the  notion  of function
␈⊃application in a slight, but significant, way.  The generalization is
␈⊃called partial application. Given a function
␈⊃
␈⊃                       f <= λ[[x1; ... ;xn] x]
␈⊃
␈⊃we  compute  a new  function  of n-m  arguments  by applying  f  to m
␈⊃arguments:
␈⊃
␈⊃                f[t1; ...; tm] <= λ[[x1; ...xn-m] x']
␈⊃
␈⊃
␈⊃where x' results form x by binding xn-(m+1) through xn to  t1 through
␈⊃tm respectively.
␈⊃
␈⊃Further  generalizations   of  partial  application   are  imaginable
␈⊃([Sta 74]).
␈⊃
␈⊃We have pointed  out several "procedural" differences.  Our treatment
␈⊃of  conditional  expressions  differs  from  the  usual  treatment of
␈⊃function application: our  standard rule for function  application is
␈⊃"call by value" which requires the evaluation of all arguments before
␈⊃calling the LISP  function, whereas only some  of the arguments  to a
␈⊃conditional expression are evaluated. Note that the whole question of
␈⊃"evaluation of arguments" is a procedural one; arguments to functions
␈⊃aren't "evaluated" or "not evaluated", they just "are".
␈⊃
␈⊃We have  seen different algorithms  computing the same  function; for
␈⊃example   fact   and  fact1 (page )   both   compute   the  factorial
␈⊃function. If there are different algorithms for  computing factorial,
␈⊃then  there are  different alorithms  for computing  the value  of an
␈⊃expression, and eval is just one such algorithm.  Just as the essence
␈⊃of fact and  fact1 is the factorial  function, we should  capture the
␈⊃essence  expressed  in  eval.   Put  another  way,  when applications
␈⊃programmers use sqrt or p they have a specific  mathematical function
␈⊃or constant  in mind.   The implementation  of the  language supplies
␈⊃approximations  to  these  mathematical  entities,  and  assuming the
␈⊃computations stay  within the  numerical ranges  of the  machine, the
␈⊃programmers  are  free to  interpret  any output  as  that  which the
␈⊃mathematical entities would produce. More importantly the programmers
␈⊃have placed specific interpretations  or meanings on symbols.  We are
␈⊃interested in doing  the same thing only  we wish to produce  a freer
␈⊃interpretation, which only  mirrors the essential ingredients  of the
␈⊃language  constructs.  That  is, sqrt  represents  a  function  and p
␈⊃represents a constant.  The eval-apply pair gives  one interpretation
␈⊃to the meaning  of functions and  constants, but there  are different
␈⊃interpretations and there are different eval-apply pairs.
␈⊃.1                                        Review and Reflection     5
␈⊃
␈⊃
␈⊃The  remainder  of this  section  will resolve  some  of  the tension
␈⊃between  function   and  procedure.  We   will  study  some   of  the
␈⊃mathematical implications of  applicative languages. First  this will
␈⊃be  done using  the λ-calculus,  a formal  language for  studying the
␈⊃concept of function. Then the  results of this study will  be applied
␈⊃to the applicative LISP subset.
␈⊃
␈⊃What  does this  discussion have  to do  with  programming languages?
␈⊃Clearly the order of evaluation or reduction is  directly applicable.
␈⊃Our  study  will also  give  insights into  the  problem  of language
␈⊃specification.  Do we say that the language specification consists of
␈⊃a  syntactic  component and  some  description of  the  evaluation of
␈⊃constructs in that language?  Or do we say that these two components,
␈⊃syntax  and  a  machine,   are  merely  devices  for   describing  or
␈⊃formalizing  notions  about  some abstract  domain  of  discourse?  A
␈⊃related question for programmers and language designers  involves the
␈⊃ideas of correctness and equivalence of programs. How do we know when
␈⊃a  program  is correct?   This  requires some  notion  of  a standard
␈⊃against which to test our implementations 4. If our algorithms really
␈⊃are  reflections of  functional  properties, then  we  should develop
␈⊃methods  for verifying  that those  properties are  expressed  in our
␈⊃algorithms. Against  this we must  balance the realization  that many
␈⊃programs  don't fit  neatly  into this  mathematical  framework. Many
␈⊃programs are best characterized as themselves. In this case we should
␈⊃then be interested in verfying equivalence of programs. If we develop
␈⊃a new algorithm we  have some responsibility to demonstrate  that the
␈⊃algorithms are equivalent in very well defined ways 5.
␈⊃
␈⊃The study  of formal  systems in  mathematical logic  offers insight.
␈⊃There, we  are presented  with a syntax  and a  system of  axioms and
␈⊃rules  of  inference.  Most  usually  we are  also  offered  a "model
␈⊃theory" which  gives us interpretations  or models for  the syntactic
␈⊃formal system; the model theory usually supplies additional  means of
␈⊃giving convincing  arguments for  the validity  of statements  in the
␈⊃formal  system.   The arguments  made  within the  formal  system are
␈⊃couched  in terms  of "provability"  whereas arguments  of  the model
␈⊃theory are  given in  terms of  "truth". For  a discussion  of formal
␈⊃systems and model theory see [Men 64].
␈⊃
␈⊃
␈⊃
␈⊃________________
␈⊃ 4 "Unless there is a prior mathematical definition of a  language at
␈⊃hand,  who   is  to   say  whether   a  proposed   implementation  is
␈⊃correct?" [Sco 72].
␈⊃
␈⊃ 5  Current  theory  is   inadequate  for  dealing  with   many  real
␈⊃programming   tasks.  However   the  realization   that  one   has  a
␈⊃responsibility  to  consider  the questions,  even  informally,  is a
␈⊃sobering one which more programmers should experience.
␈⊃6                                                                  .1
␈⊃
␈⊃
␈⊃
␈⊃C.  W. Morris  ([Mor 55])  isolated three  perspectives  on language,
␈⊃syntax, pragmatics, and semantics.
␈⊃
␈⊃Syntax: The synthesis and  analysis of sentences in a  language. This
␈⊃        area   is    well   cultivated   in    programming   language
␈⊃        specification.
␈⊃
␈⊃Pragmatics:  The  relation   between  the  language  and   its  user.
␈⊃           Evaluators, like tgmoaf, tgmoafr and eval, come  under the
␈⊃           heading  of  pragmatics.   Pragmatics  are  more  commonly
␈⊃           referred  to as  operational semantics  in  discussions of
␈⊃           programming languages.
␈⊃
␈⊃Semantics: The  relation between constructs  of the language  and the
␈⊃          abstract  objects which  they denote.  This  subdivision is
␈⊃          commonly referred to as denotational semantics.
␈⊃
␈⊃Put  differently, syntax  describes appearance,  pragmatics describes
␈⊃implementation, and semantics describes meaning 6.  Though there is a
␈⊃strong concensus on  the syntactic tools for  specifying languages 7,
␈⊃there is no concensus on adequate pragmatics and even  less agreement
␈⊃on the adequancy of semantic descriptions.  We will first outline the
␈⊃pragmatics questions  and then  discuss a bit  more of  the semantics
␈⊃issues.  In this discussion we will use the language  distinctions of
␈⊃Morris  even though  the  practice is  not commonly  followed  in the
␈⊃literature.  Typically, syntax is studied precisely and  semantics is
␈⊃"everything  else";  however  the  distinction   between  computation
␈⊃(pragmatics) and  truth (semantics)  is important  and should  not be
␈⊃muddled.
␈⊃
␈⊃One thought is to describe  the pragmatics of a language in  terms of
␈⊃the process of compilation.  That is, the pragmatics is  specified by
␈⊃some canonical compiler  producing code for some  well-defined simple
␈⊃machine.  The meaning of a  program is the outcome of  an interpreter
␈⊃interpreting this  code.  But  then, to understand  the meaning  of a
␈⊃particular  construct,  this  proposal  requires  that  you  read the
␈⊃
␈⊃________________
␈⊃ 6 This division of language reflects an interesting parallel between
␈⊃mathematical logic and  programming languages: in  mathematical logic
␈⊃we have  deduction, computation, and  truth; in  programming language
␈⊃specification we have three analogous schools of  thought: axiomatic,
␈⊃operational, and denotational. We won't go into details here, but see
␈⊃[Men 64] for the mathematical logic  and [Dav 76] for a study  of the
␈⊃interrelationships; see  [Hoa 69] for a  discussion of  the axiomatic
␈⊃school;  we  will say  more  about the  operational  and denotational
␈⊃schools in this section.
␈⊃
␈⊃ 7 But see [Pra 73] for a contrary position.
␈⊃.1                                        Review and Reflection     7
␈⊃
␈⊃
␈⊃description of  a compiler  and understand  the simple  machine.  Two
␈⊃problems   arise   immediately.   Compilers   are   not  particularly
␈⊃transparent  programs.  Second,   a  very  simple  machine   may  not
␈⊃adequately reflect the  actual mechanisms used in  an implementation.
␈⊃This  aspect  is  important  if the  semantic  description  is  to be
␈⊃meaningful to an implementor.
␈⊃
␈⊃There  is  a more  fundamental  difficulty here  if  we  consider the
␈⊃practical  aspects  of this  proposal.   When asked  to  understand a
␈⊃program written in a high-level language you think about the behavior
␈⊃of that program in a very direct way. The pragmatics is close  to the
␈⊃semantics.   You  think  about  the  computational  behavior   as  it
␈⊃executes; you  do not think  about the code  that gets  generated and
␈⊃then think about the execution of that code.
␈⊃
␈⊃A more natural pragmatics for the constructs is given in terms of the
␈⊃run-time behavior of  these constructs.  The eval  function describes
␈⊃the  execution  sequence of  a  representation of  an  arbitrary LISP
␈⊃expression.  Thus  eval is  the semantic  description of  LISP.  Such
␈⊃descriptions   have  a   flavor  of   circularity  which   some  find
␈⊃displeasing. However some  circularity is inevitable; we  must assume
␈⊃that something is known and does not require further explication.  If
␈⊃you decide  to describe  the semantics  of language  L1 in  a simpler
␈⊃language  L2 then  either L2  is "self evident"  or you  must  give a
␈⊃description of the meaning of L2.
␈⊃
␈⊃So, realistically, the choice is where to stop, not whether  to stop.
␈⊃The  LISP  position is  that  the language  and  data  structures are
␈⊃sufficiently simple that self-description is  satisfactory.  Attempts
␈⊃have been made to give non-circular interpreter-based descriptions of
␈⊃semantics for languages other than LISP.  There is a  VDL description
␈⊃of PL/1, and  a description of ALGOL  68 by a Markov  algorithm. Both
␈⊃these attempts result in a description which is long and unmanageable
␈⊃for all but the most persistent.
␈⊃
␈⊃There are some compelling reasons for deciding on direct circularity.
␈⊃First, you  need only  learn one language.   If the  specification is
␈⊃given the  source language, then  you learn the  programming language
␈⊃and the specification language  at the same time.  Second,  since the
␈⊃evaluator is written in the language, we can understand  the language
␈⊃by understanding the workings of the single program, eval; and  if we
␈⊃wish to modify the pragmatics, we need change only one  collection of
␈⊃high-level programs.  If we wished to add new language  constructs to
␈⊃LISP we need only modify eval so that it recognizes an  occurrence of
␈⊃that new construct, and add a function to describe the interpretation
␈⊃of the construct.  That modification might be extensive, but  it will
␈⊃be a controlled revision rather than a complete reprogramming effort.
␈⊃
␈⊃We should mention that there is another common method  for specifying
␈⊃8                                                                  .1
␈⊃
␈⊃
␈⊃the pragmatics of a  programming language. The original  Algol report
␈⊃([Alg 63]) introduced  a standard for  syntax specification:  the BNF
␈⊃equation.  It  also  gave a  reasonably  precise  description  of the
␈⊃pragmatics of  Algol statements  in natural  language.  The  style of
␈⊃presentation was concise and  clear, but suffers for  the imprecision
␈⊃of natural language.  Regardless, this style of description  is quite
␈⊃common  and  is  very useful.  A  recent  report ([Moor 75a])  on the
␈⊃pragmatics of InterLISP used this descriptive style.  If the language
␈⊃is quite complex, then  a formal description can be  equally complex.
␈⊃In  Section   we  will  see  that  our  interpreter  definition  will
␈⊃extend nicely to richer subsets of LISP.
␈⊃
␈⊃A language description should not attempt to explain everything about
␈⊃a language.  It  need only explain  what needs explaining.   You must
␈⊃assume  that  your  reader  understands  something  ...  .  McCarthy:
␈⊃`Nothing can be explained  to a stone' [McC 66].  A description  of a
␈⊃language must be natural and  must match the expressive power  of the
␈⊃language.  That is,  it should  model how  the constructs  are  to be
␈⊃implemented.  What is needed is a description which  exploits, rather
␈⊃than ignores, the  structure of the language.   Mathematical notation
␈⊃is  no  substitute  for   clear  thought,  but  we   believe  careful
␈⊃formulations of  semantics will  lead to  additional insights  in the
␈⊃pragmatics of language design  8.  The task requires new mathematical
␈⊃tools to model the  language constructs, and requires  increased care
␈⊃on the part of the language designer.
␈⊃
␈⊃Let's  look at  the issue  of syntax  for a  moment.   A satisfactory
␈⊃description of much of  programming language syntax is  standard BNF.
␈⊃The  BNF  is  a  generative,  or  synthetic  grammar.   That  is, the
␈⊃rewriting rules specifying how  to generate well formed  strings.  An
␈⊃evaluator, on the other hand, takes a program representation as input
␈⊃and gives as  output a representation of  the value of  executing the
␈⊃program.  This  implies that  our evaluator  is analytic  rather than
␈⊃synthetic; it must  have some way of  analyzing the structure  of the
␈⊃program.
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃________________
␈⊃ 8  R.  D.  Tennent  has  invoked  this  approach  in  the  design of
␈⊃QUEST ([Ten 76]).
␈⊃.1                                        Review and Reflection     9
␈⊃
␈⊃
␈⊃
␈⊃In  [McC 62],  John  McCarthy  introduced  the  concept  of  abstract
␈⊃analytic  syntax.  The  idea  is  directly  derivable  from  the LISP
␈⊃experience. The  syntax is  analytic, rather  than synthetic,  in the
␈⊃sense that it  tells how to  take programs apart -- how  to recognize
␈⊃and  select  subparts  of  programs  using  predicates  and  selector
␈⊃functions 9.  It is abstract in the sense that it makes no commitment
␈⊃to the external representation  of the constitutents of  the program.
␈⊃We  need  only be  able  to  recognize the  occurrence  of  a desired
␈⊃construct. For example:
␈⊃
␈⊃isterm <= λ[[t] or[ isvar[t];
␈⊃               isconst[t];
␈⊃               and[issum[t];isterm[addend[t]];isterm[augend[t]]]]]
␈⊃
␈⊃and the BNF equation:
␈⊃
␈⊃            <term> ::= <var> | <const> | <term> + <term>.
␈⊃
␈⊃issum,  addend, and  augend,  don't really  care whether  the  sum is
␈⊃represented as x+y,  or +[x;y] or  (PLUS X Y) or xy+.   The different
␈⊃external  representations  are  reflections  of   different  concrete
␈⊃syntaxes;  the above  BNF equations  give one.   It is  parsing which
␈⊃links a concrete syntax with the abstract syntax.
␈⊃
␈⊃Since the evaluator must  operate on some internal  representation of
␈⊃the source program, a representation reflecting the structure  of the
␈⊃program (commonly  known as  a parse  tree) is  most natural.  We can
␈⊃describe the  evaluation of a  program in terms  of a  function which
␈⊃operates on a  parse tree using the  predicates and selectors  of the
␈⊃analytic  syntax. Abstract  syntax  concerns itself  only  with those
␈⊃properties of a program which are of interest to an evaluator.
␈⊃
␈⊃The  Meta  expression  LISP  constitutes  a  concrete   syntax.   The
␈⊃M-to-S-expression translator  is the parser  which maps  the external
␈⊃representation onto  a parse (or  computational) tree.  The selectors
␈⊃and predicates of the analytic syntax are straightforward. Recall the
␈⊃BNF for LISP:
␈⊃
␈⊃<form>    ::= <constant>
␈⊃          ::= <variable>
␈⊃          ::=<function>[<arg>; ... ;<arg>]
␈⊃          ::= [<form> → <form>; ... ;<form> → <form>]
␈⊃            ....
␈⊃
␈⊃
␈⊃________________
␈⊃
␈⊃ 9  We  will deal  with  abstract synthetic  syntax  when  we discuss
␈⊃compilers.
␈⊃10                                                                 .1
␈⊃
␈⊃
␈⊃Our  evaluator  needs  to  recognize  constants  (isconst), variables
␈⊃(isvar),   conditional   expressions   (iscond),   and   applications
␈⊃(isfun+args).   We  need  to  write  a  function  in   some  language
␈⊃expressing  the  values  of  these  constructs.  Since  the  proposed
␈⊃evaluator is to manipulate parse trees, and since the domain  of LISP
␈⊃functions is binary trees, it should seem natural to use LISP itself.
␈⊃If this is the case, then we must represent the parse tree as  a LISP
␈⊃S-expr and represent the selectors and recognizers as  LISP functions
␈⊃and predicates.
␈⊃Perhaps:
␈⊃
␈⊃isconst <= λ[[x] or[ numberp[x];
␈⊃               eq[x;NIL];
␈⊃               eq[x;T];
␈⊃               and[not[atom[x]];eq[first[x];QUOTE]
␈⊃
␈⊃isvar <= λ[[x] and[atom[x]; not[isconst[x]]]]
␈⊃
␈⊃iscond <= λ[[x] eq[first[x];COND]]
␈⊃
␈⊃
␈⊃There are really  two issues here:  a representation of  the analytic
␈⊃syntax of a language, and  a representation in terms of  the language
␈⊃itself.   In conjunction,  these two  ideas give  a natural  and very
␈⊃powerful means of specifying languages.
␈⊃
␈⊃If this style of specification is really effective, then we should be
␈⊃able to specify other languages  in a similar fashion.  What  are the
␈⊃weak points  of LISP  as a `real'  programming language?   Mainly the
␈⊃insistence on binary tree representations of data.  Many applications
␈⊃could profit  from other  data representations.   What we  would then
␈⊃like is  a language which  has richer data  structures than  LISP but
␈⊃which is designed  to allow LISP-style semantic  specification.  That
␈⊃is, we would be able to give an analytic syntactic  specification for
␈⊃the language.  We  would be able to  write an evaluator,  albeit more
␈⊃complex  than  eval, in  the  language itself.   The  evaluator would
␈⊃operate on a representation of the program as a data structure of the
␈⊃language, just as eval operates on the S-expr translation of the LISP
␈⊃program.  The  concrete syntax  would be  specified as  a set  of BNF
␈⊃equations, and our parser would translate legal strings -- programs--
␈⊃into parse trees.
␈⊃In outline, to  specify a construct  we must have at least the follow
␈⊃
␈⊃                             1.  A concrete production.
␈⊃                             2.  An abstract data type.
␈⊃                             3.  A mapping from 1 to 2.
␈⊃                             4.  An evaluator for the abstract type.
␈⊃.1                                       Review and Reflection     11
␈⊃
␈⊃
␈⊃In Chapter  we  will sketch a  recent LISP-like language,  EL1, which
␈⊃does follow these rules 10.
␈⊃
␈⊃From a discussion  of syntax we  have gravitated back  to evaluation.
␈⊃After we reduce the  questions of syntax of programming  languages to
␈⊃questions of abstract  systax and strip  away many of  the irrelevant
␈⊃syntactic differences,  how many  real differences  between languages
␈⊃are there?  Semantics addresses this issue.
␈⊃
␈⊃Many of the semantic  ideas in applicative programming  languages can
␈⊃be captured in λ-calculus ([Chu 41]). The λ-calculus was developed to
␈⊃supply  a  formalism  for  discussing  the  notions  of  function and
␈⊃function application. Rather  than develop the syntax  of λ-calculus,
␈⊃we will use the syntax of LISP; we will show how we can abstract from
␈⊃the discussion  of LISP  function, or procedure,  to a  discussion of
␈⊃mathematical function.
␈⊃
␈⊃LISP  has  borrowed  heavily, but  informally,  from  mathematics and
␈⊃logic.  It uses the language of functions and functional composition,
␈⊃but the usual interpretation  of LISP expressions is  procedural.  We
␈⊃want to relate  that procedural knowledge  to the formalism  which we
␈⊃have been developing.
␈⊃
␈⊃Most  of  the description  of  LISP which  we  have given  so  far is
␈⊃classified as  operational.  That means  our informal  description of
␈⊃LISP and our later description of the LISP interpreter are couched in
␈⊃terms of "how  does it work or  operate". Indeed the purpose  of eval
␈⊃was to  describe explicitly  what happens when  a LISP  expression is
␈⊃evaluated.   We  have  seen  (page )  that  discussion  of evaluation
␈⊃schemes is non-trivial; and  that order of evaluation can  effect the
␈⊃outcome (page ).
␈⊃
␈⊃However, many times the order of evaluation is immaterial  11. We saw
␈⊃on  page   that eval  will  perform without  complaint  when  given a
␈⊃form f[ ... ] supplied with too many arguments.  How much of  eval is
␈⊃"really" LISP and how  much is "really" implementation?  On  one hand
␈⊃we have said that the  meaning of a LISP expression  is by definition
␈⊃what eval will calculate using the representation of  the expression.
␈⊃On the other  hand, we claim that  eval is simply  an implementation.
␈⊃There are certainly other implementations; i.e, other  LISP functions
␈⊃evali  which have  the same  input-output behavior  as our  eval. The
␈⊃position here is  not that eval is  wrong in giving values  to things
␈⊃like cons[A;B;C], but rather: just what is it that eval implements?
␈⊃
␈⊃
␈⊃________________
␈⊃ 10 Compare steps 1 through 4 with those on page .
␈⊃
␈⊃ 11  "One  difficulty  with  the  operational  approach  is  that  it
␈⊃(frequently) specifies too much": C. Wadsworth.
␈⊃12                                                                 .1
␈⊃
␈⊃
␈⊃This  other way  of looking  at meaning  in programming  languages is
␈⊃exemplified   by  denotational   or  mathematical   semantics.   This
␈⊃perspective  springs from  a common  mathematical,  philosophical, or
␈⊃logical  device of  distinguishing  between a  representation  for an
␈⊃object,  and  the  object  itself.  The  most  usual  guise   is  the
␈⊃numeral-number  distinction.   Numerals  are  notations  (syntax) for
␈⊃talking about numbers (semantics).   thus the Arabic numerals  2, 02,
␈⊃the  Roman numeral  II, and  the Binary  notation 10,  all  denote or
␈⊃represent  the number  two. In  LISP, (A B),  (A . (B)),  (A , B) and
␈⊃(A . (B . NIL)) all are  notations for the  same S-expr.  We  want to
␈⊃say  that  a LISP  form  car[(A . B)] denotes  A,  or  car[A] denotes
␈⊃undefined just as we say in mathematics that 2+2 denotes 4 or  1/0 is
␈⊃undefined.
␈⊃
␈⊃Similarly, we will  say that the  denotational counterpart of  a LISP
␈⊃function  is  a  mathematical function  defined  over  an appropirate
␈⊃abstract domain.  Before proceeding, we introduce some conventions to
␈⊃distinguish notation from denotation.
␈⊃
␈⊃We will continue to use italics:
␈⊃
␈⊃                  A, B, ..., x, ... car, ...(A . B)
␈⊃
␈⊃as notation in LISP expressions.  Gothic bold-face:
␈⊃
␈⊃                  A, B, ..., x, ...car, ...(A . B)
␈⊃
␈⊃will represent denotations.
␈⊃
␈⊃Thus A is notation for A; car[cdr[(A B)]] denotes B; the mapping, car
␈⊃is the denotation of the LISP function car.
␈⊃
␈⊃Several areas of LISP must be subjected to an abstraction process.
␈⊃
␈⊃In particular, the operations involved in the evaluation process must
␈⊃be abstracted away. These involve an abstraction from LISP's  call by
␈⊃value  evaluation  and  its  left to  right  order  of  evaluation of
␈⊃arguments.   For  example,  the  operation  of  composition  of  LISP
␈⊃functions   is   to  denote   mathematical   composition;   in  LISP,
␈⊃car[cdr[(A B)]] means apply the  procedure cdr to the  argument (A B)
␈⊃getting (B); apply the procedure car to (B) getting B. Mathematically
␈⊃speaking, we have  a mapping, carocdr which  is a composition  of the
␈⊃car and cdr mappings; the ordered pair <(A B) , B> is in the graph of
␈⊃this  composed  mapping.   At this  level  of  abstraction,  any LISP
␈⊃characterization  of a  function  is equally  good;  the "efficiency"
␈⊃question has been abstracted away. Many important properties  of real
␈⊃programs  can   be  discussed  in   this  mathematical   context;  in
␈⊃particular, questions of equivalence and correctness of  programs are
␈⊃more approachable.
␈⊃.1                                       Review and Reflection     13
␈⊃
␈⊃
␈⊃As  we remove  the operational  aspects, we  discover a  few critical
␈⊃properties  of  functions  which  must  be  reconcilled  with  LISP's
␈⊃procedures. We must treat the  ideas of binding of variables,  and we
␈⊃must handle the notion of function application.
␈⊃
␈⊃We  already  know that  there  are at  least  two  binding strategies
␈⊃available: static binding and dynamic binding. The choice of strategy
␈⊃can effect the  resultant computation. This  computational difference
␈⊃must be  studied.  Similarly, we  have discussed two  calling styles:
␈⊃call-by-value and call-by-name; these computational devices must have
␈⊃interpretations in mathematics. To illuminate the problem we  take an
␈⊃example in LISP.
␈⊃
␈⊃Consider:
␈⊃         λ[[z]
␈⊃             λ[[u]
␈⊃                 λ[[z] u[B]][C]]
␈⊃               [λ[[x] cons[x;z]]] ]
␈⊃           [A]
␈⊃
␈⊃
␈⊃The dynamic binding strategy of LISP will bind z to A; then bind u to
␈⊃the functional argument, λ[[x] cons[x;z]];  next, z is rebound  to C,
␈⊃and  finally u[B]  is evaluated.  That involves  binding x  to  B and
␈⊃evaluating cons[x;z]. Since we are using dynamic binding,  the latest
␈⊃bindings of the variables are  used. The latest bindings for x  and z
␈⊃are B and C respectively, and the final value is therefore (B . C).
␈⊃
␈⊃We  can  obtain  static  binding  by  replacing  λ[[x] cons[x;z]]  by
␈⊃function[λ[[x] cons[x;z]]]. This  has the  effect of  associating the
␈⊃varaible z  with the atom  A.  As  we know, the  final result  of the
␈⊃computation will be (B . A).
␈⊃
␈⊃Which scheme more adequately reflects our intuitions about properties
␈⊃of function?   The dynamic  binding scheme  implements the  notion of
␈⊃procedure application f[t]  being the substitution  of a copy  of the
␈⊃body f with every occurrence of a formal parameter replaced with t.
␈⊃
␈⊃This substitution can be expressed as:
␈⊃
␈⊃subst <= λ[[x;y;z]       [is_var[z] → [equal[y;z] → x; t → z];
␈⊃                 is_app[z] → mk_app[ subst[x;y;func[z]]
␈⊃                                  subst[x;y;arglist[z]]];
␈⊃                 eq[y;λ_var[z]] → z;
␈⊃                 t → mk_λ[        λ_var[z];
␈⊃                         subst[x;y;body[z]]]]]
␈⊃
␈⊃However, some  implications of dynamic  binding are in  conflict with
␈⊃other  properties we  expect functions  to possess.  For  example, we
␈⊃14                                                                 .1
␈⊃
␈⊃
␈⊃expect that  a systematic remaining  of formal parameters  should not
␈⊃effect the definition of a function.
␈⊃
␈⊃     λ[[y] x]    should denote the same function as    λ[[w] x].
␈⊃
␈⊃But
␈⊃
␈⊃   λ[[x] λ[[y] x]][w]   is not the same as     λ[[x] λ[[w] x]][w]
␈⊃
␈⊃Using  dynamic binding,  the first  results in  λ[[y] w],  the second
␈⊃gives λ[[w] w].
␈⊃
␈⊃If we  wish to  ban such  anomalies, we  need to  be more  careful in
␈⊃performing our substitutions.  Closer examination of the last example
␈⊃shows  that the  result  λ[[w] w] occurred  because  the substitution
␈⊃changed  a non-local  name (x)  into a  local name  (w).   The expect
␈⊃result would  have been obtained  if we had  recognized the  clash of
␈⊃names and replaced the formal parameter y with a new name, say u, and
␈⊃then performed the substitution,  getting λ[[u] w] which is  the same
␈⊃as λ[[y] w].
␈⊃
␈⊃Before giving a substitution rule which accounts for such  changes of
␈⊃name we will relate this discussion with the notions of logic.
␈⊃
␈⊃A variable, x, is a free variable 12 in a <wfe>, x if:
␈⊃
␈⊃x is the variable, x.
␈⊃
␈⊃x is an application, f[A], and x is free in f or x is free in A.
␈⊃
␈⊃x is a λ-expression, λ[[y] M], and  x is free in M and x  is distinct
␈⊃          from y.
␈⊃
␈⊃Thus w is free in λ[[x] w].
␈⊃
␈⊃We can definea LISP predicate to test for free-ness:
␈⊃
␈⊃isfree <= λ[[x;z]         [is_var[z] → [eq[x;z] → t; t → f]
␈⊃               is_app[z] →  [isfree[x;func[z]] → t;
␈⊃                           t → isfree[x;arglist[z]]];
␈⊃               eq[λ_var[z];x] → f;
␈⊃               t → isfree[x;body[z]]]]
␈⊃
␈⊃
␈⊃A variable is a bound variable in x if it occurs in x and is not free
␈⊃in x.  For example, w is bound in λ[[w] w].
␈⊃
␈⊃
␈⊃________________
␈⊃ 12 Compare this definition of free with that in Section .
␈⊃.1                                       Review and Reflection     15
␈⊃
␈⊃
␈⊃Using our new terminology, we  can say that a substitution  of actual
␈⊃parameter for formal parameter can be made provided no free variables
␈⊃of the actual  parameter will become bound  variables as a  result of
␈⊃the substitution.
␈⊃
␈⊃Here is an appropriate substitution rule:
␈⊃
␈⊃subst' <= λ[[x;y;z]  [is_var[z] → [eq[y;z] → x; t → z];
␈⊃                is_app[z] → mk_app[  subst'[x;y;func[z]];
␈⊃                                 subst'[x;y;arglist[z]]];
␈⊃                eq[y;λ_var[z]] → z;
␈⊃                not[isfree[y;body[z]]] → z;
␈⊃                not[isfree[λ_var[z];x]] → mk_λ[    x
␈⊃                                           λ_var[z];
␈⊃                                           subst[
␈⊃                                                   y;
␈⊃                                                   body[z]]];
␈⊃                t →  λ[[u] mk_λ[ u;
␈⊃                               subst'[     x;
␈⊃                                     y;
␈⊃                                     subst'[       u;
␈⊃                                           λ_var[z];
␈⊃                                           body[z]]]]]
␈⊃                       [genvar[ ]] ]]
␈⊃
␈⊃where genvar  is to  supply a new  identifier for  use as  a variable
␈⊃name.
␈⊃
␈⊃LISP does  not use  explicit substitution,  but rather  simulates the
␈⊃substitution  using  symbol  tables.  LISP  embodies  subst  with the
␈⊃exception that  the function construct simulates  subst'. As  we have
␈⊃seen, the  use of subst  can lead to  unexpected results.  The formal
␈⊃study of functionality  which appears in  the λ-calculus is  based on
␈⊃subst'.
␈⊃
␈⊃The substitution rule, subst', is used to expressed the b-rule of the
␈⊃λ-calculus:
␈⊃
␈⊃(b): app ≡ subst'[arglist[app];λ_var[func[app]];body[func[app]]]
␈⊃
␈⊃where  app  is  an  anonymous  λ-application,  and  ≡   designates  a
␈⊃replacement  rule  which  maintains  the  intended  meaning   of  the
␈⊃expressions.
␈⊃16                                                                 .1
␈⊃
␈⊃
␈⊃
␈⊃There is another basic rule of the λ-calculus called the a-rule.  The
␈⊃a-rule generalizes the notion that λ[[x] x] denotes the same function
␈⊃as λ[[w] x]; that is, subject to certain restrictions, we  can change
␈⊃the formal parameter. The a-rule says:
␈⊃
␈⊃(a):   fun ≡ λ[[u] mk_λ[u;subst'[u;λ_var[fun];body[fun]]][var]
␈⊃
␈⊃
␈⊃provided that not[isfree[var];body[fun]].
␈⊃
␈⊃To complete  the descrption,  axioms which govern  the behavior  of ≡
␈⊃must be given.  Essentially ≡ acts as equality: it is  an equivalence
␈⊃relation.   The  a  and b-rules  are  called  conversion  rules; they
␈⊃express the essential  behavior of functions and  their applications.
␈⊃The a rule formalizes the  notion that a function is unchanged  if we
␈⊃change its formal parameters. This property is related to referential
␈⊃transparency.  A language  possesses referential transparency  if the
␈⊃value of an expression which contains subexpressions depends  only on
␈⊃the values of those subexpressions.  For example, if the value  of an
␈⊃expression changed because we changed  the name of a variable  in one
␈⊃of   its  subexpressions,   then  that   would   violate  referential
␈⊃transparency.   This means  LISP is  not a  referentially transparent
␈⊃language; for a  fixed environment, the value  of λ[[x] cons[x;y]][A]
␈⊃is  the same  as λ[[z] cons[x;y]][A],  but need  not be  the  same as
␈⊃λ[[x] cons[x;z]][A].  The difficulty  again is the treatment  of free
␈⊃variables:  dynamic  binding violates  referential  transparency. The
␈⊃λ-calculus  is   statically  bound   and  does   possess  referential
␈⊃transparency.  Referential transparency  is not  simply  a worthwhile
␈⊃theoretical property; its corollary, static binding, is a very useful
␈⊃practical  property. In  programming terms,  referential transparency
␈⊃mens that to understand  a particular progam we need  only understand
␈⊃the   effect  of   the   subprograms  rather   than   understand  the
␈⊃implementation of those subprograms. This idea is closely  related to
␈⊃the notion of modular programming.  We will discuss some implications
␈⊃of static binding in Section  13.
␈⊃
␈⊃The b-rule  expresses the intent  of function application.   We would
␈⊃then  expect that  a  model of  the  λ-calculus would  have  a domain
␈⊃consisting of functions; and require that the b-rule be  reflected in
␈⊃the model as function application. The equality preserving properties
␈⊃of the a and b rules would require that if f[a] = g[a] in  the model,
␈⊃then any a  or b manipulations of  those expressions will  not affect
␈⊃that equality.
␈⊃
␈⊃The discussion has centered around binding strategies. We  should now
␈⊃
␈⊃________________
␈⊃ 13  There have  been recent  investigations ([Hew 76],  [Sus 75]) of
␈⊃statically bound LISP-like languages.
␈⊃.1                                       Review and Reflection     17
␈⊃
␈⊃
␈⊃discuss  calling style.   The conversion  rules have  no restrictions
␈⊃concerning an order of application.  If the hypotheses for a  rule is
␈⊃satisfied, then it may be applied.  That is as it should be.   In the
␈⊃reduction of a λ-expression there may be several  reductions possible
␈⊃at any  one time.   If we design  a λ-calculus  machine, it  might be
␈⊃specified with a  preferred order. The machine  reflects "procedure";
␈⊃the calculus reflects "function".
␈⊃
␈⊃An interpretation  of the conversion  rules as rules  of compuatation
␈⊃requires the  establishment of a  notion of what  is to  be computed.
␈⊃The conversion rules  simply state equivalences  between expressions.
␈⊃When the b rule is applied in a manner analogous to LISP's λ-binding,
␈⊃that is, to replace an application with the appropriately substituted
␈⊃body, then the b rule is called a reduction rule; and the application
␈⊃of  the  rule is  called  a  reduction step.   There  are  two common
␈⊃strategies  for  choosing  a reduction  step:  applicative  order and
␈⊃normal order.
␈⊃
␈⊃Applicative order corresponds to call-by-value, reducing the argument
␈⊃before  reducing  the  function. Normal  order  reduces  the function
␈⊃application first; this corresponds to call-by-name. As we  know from
␈⊃LISP,  the order  of evaluation  can influence  the termination  of a
␈⊃computation. That observation holds for the λ-calculus. To understand
␈⊃what corresponds to termination in the formalism, requires a bit more
␈⊃terminology.  We will say that a λ-expression is in normal form if it
␈⊃contains no expression reducible by the β rule.  Not  all expressions
␈⊃have normal forms: let D name λ[[x] x[x]]; then D[D] does not  have a
␈⊃normal form.  Every b  transformation of D produces a  new expression
␈⊃which  is also  b  reducible.  Not  all reduction  sequences  yield a
␈⊃normal form: when  λ[[x] y][D] is reduced  in normal order,  a normal
␈⊃form results; whereas applicative order will not yield a normal form.
␈⊃
␈⊃The  application of  the reduction  rules, in  either  applicative or
␈⊃normal order, can be considered a computation scheme. The  process of
␈⊃reducing  an  expression  is  the  computation,  and   a  computation
␈⊃terminates  if  that reduction  produces  a normal  form.   With this
␈⊃interpretation, some computations  terminate and some don't.  A <wfe>
␈⊃has  a  normal  form  just in  the  case  that  a  reduction sequence
␈⊃terminates.  The computational interpretation of the λ-calculus rules
␈⊃can be extended to the development of λ-calculus machines ([Lan 64]).
␈⊃The λ-calculus machines can  simulate the reduction rules  in several
␈⊃ways; since the  rules make no  commitment for order  of application,
␈⊃that  choice is  open.  Also,  the reduction  rules are  described in
␈⊃terms of substitutions; a machine might simulate this  facet directly
␈⊃or might employ a mechanism like the LISP symbol table.
␈⊃
␈⊃This discussion suggests some interesting problems.  First, there may
␈⊃well  be two  or  more sequences  of reductions  for  a λ-expression;
␈⊃assuming they terminate,  is there any  reason to believe  that these
␈⊃18                                                                 .1
␈⊃
␈⊃
␈⊃reduction sequences will yield the same normal forms?  Second,  if we
␈⊃have  two λ-expressions  which reduce  to distinct  normal  forms, is
␈⊃there  any reason  to believe  that they  are  "inherently different"
␈⊃λ-expressions?
␈⊃
␈⊃The first question is easier to answer.  If both  reduction sequences
␈⊃terminate then they  result in the same  normal form. This  is called
␈⊃the Church-Rosser theorem.
␈⊃
␈⊃The  second  question   requires  some  explanation   of  "inherently
␈⊃different".   At  one  level   we  might  say  that   by  definition,
␈⊃expressions with different  normal forms are  "inherently different".
␈⊃But thinking of λ-expressions as functions, to say  two λ-expressions
␈⊃are "different"  is to  say we  can exhibit  arguments such  that the
␈⊃value of application of one function is not equal to the value of the
␈⊃other  function  application 14. C. Boehm  has  established  this for
␈⊃λ-expressions which have normal forms.
␈⊃
␈⊃However the situation changes when we examine λ-expressions  which do
␈⊃not have normal forms.  Recalling the intuitive  relationship between
␈⊃non-termination  and  "no  normal form",  we  might  expect  that all
␈⊃expressions without normal form are "equivalent". However, it  can be
␈⊃shown that such an  identification would lead to  contradictions.  We
␈⊃might  also  expect  that  λ  expressions  without  normal  forms are
␈⊃"different" from  expressions which  do have  normal forms.   This is
␈⊃also not true;  [Wad 71] exhibits two expressions,  I and J  with and
␈⊃without normal form, respectively.  However these two expressions are
␈⊃the "same" in the sense  that 3 and 2.99999 ... are the  "same". That
␈⊃is, J is the  limit of a sequence  of approximations to I.   Also, we
␈⊃can exhibit two λ-expressions,  Y1 and Y2, both without  normal form,
␈⊃which are distinct in that  no reduction sequence will reduce  one to
␈⊃the other; but our intuition  says that they are "the  same function"
␈⊃in the  sense that, for  any argument, a  we supply,  both reductions
␈⊃result in the same expression.  That is Y1[a] = Y2[a] 15.
␈⊃
␈⊃The reduction rules of the λ-calculus cannot help us.  The resolution
␈⊃of the  idea of  "same-ness" requires  stronger analysis 16.   We can
␈⊃give  an  interpretation  to   the  λ-calculus  such  that   in  that
␈⊃interpretation  the  pairs I  and  J, or  Y1  and Y2,  have  the same
␈⊃
␈⊃________________
␈⊃ 14  If  two  functions  satisfy  this  then  they  are  said  to  be
␈⊃extensionally equal.
␈⊃
␈⊃ 15 Note that f[a] may have a normal form even though f does not.
␈⊃
␈⊃ 16 The  interpretation of  "same-ness" which we  have been  using is
␈⊃that of extensional equality.  That is, two functions  are considered
␈⊃the same function if no differences can be detected under application
␈⊃of the functions to any arguments.
␈⊃.1                                       Review and Reflection     19
␈⊃
␈⊃
␈⊃meaning. This  should be a  convincing argument for  maintaining that
␈⊃they are the "same function" even though the reduction rules  are not
␈⊃sufficient to display that equivalence  17.  D. Scott has exhibited a
␈⊃model or interpretation of the λ-calculus, and D. Park has  shown the
␈⊃equivalence in this model of Y1 and Y2, and C. Wadsworth as shown the
␈⊃equivalence of I and J.
␈⊃
␈⊃As  we  said at  the  beginning  of the  section,  this  calculus was
␈⊃intended  to   explicate  the  idea   of  "function"   and  "function
␈⊃application".   There  is  a  reasonably  subtle  distinction between
␈⊃Church's conception of  a function as  a rule of  correspondence, and
␈⊃the usual set-theoretic view of a function as a set of ordered pairs.
␈⊃In the latter  setting we rather naturally  think of the  elements of
␈⊃the  domain  and  range  of  a  function  as  existing  prior  to the
␈⊃construction of the function:
␈⊃
␈⊃            "Let f be the function {<x,1>, <y,2>, ...}".
␈⊃
␈⊃When we think of a  function given as a predetermined set  of ordered
␈⊃pairs  we  do  not  expect functions  which  can  take  themselves as
␈⊃arguments  like  f[f]. Such  functions  are  called self-applicative.
␈⊃Several  languages, including  LISP, allow  the procedural  analog of
␈⊃self  applicative  functions.  They  are  an  instance  of functional
␈⊃arguments (Section ).    The   LISP   function   discussed   in   the
␈⊃problem  on  page   is   an  instance  of  a   self-applicative  LISP
␈⊃function.  Since  we can deal  with self-application as  a procedural
␈⊃concept at least, perhaps  some of this understanding will  help with
␈⊃the mathematical questions. Again, we turn to the λ-calculus.
␈⊃
␈⊃The calculus is an appropriate tool for studying self-application and
␈⊃function-values   since   syntax  allows   such   constructs.  Indeed
␈⊃everything in the calculus is a representation of a function. Compare
␈⊃this with  the condition in  LISP when we  think of  the S-expression
␈⊃representation  of  language   as  the  language  itself.   Then  the
␈⊃distinction between program and  data disappears, just as it  does in
␈⊃the  λ-calculus.   The  conversion  rules  of  the  calculus  allow a
␈⊃λ-expression  to  be  applied  to  any  λ-expression   including  the
␈⊃expression itself 18.
␈⊃
␈⊃A similar situation exists in the programming language LISP.   We can
␈⊃evaluate expressions like:
␈⊃
␈⊃((LAMBDA (X) x) (LAMBDA (X) x))
␈⊃________________
␈⊃ 17 This demonstration also  gives credence to the position  that the
␈⊃meaning transcends the  reduction rules.  Compare  the incompleteness
␈⊃results of K. Godel.
␈⊃
␈⊃ 18  There are  logical difficulties,  like Russell's  paradox, which
␈⊃arise if we allow unrestricted self-application.
␈⊃20                                                                 .1
␈⊃
␈⊃
␈⊃As  we  move  again  from  procedural  notions  to  more denotational
␈⊃concepts we should remark that denotational interpretations have been
␈⊃introduced before.  When we said (page ) that:
␈⊃                f[a1; ... an] = eval[(F A1 ... An)],
␈⊃
␈⊃we were  relating a denotational  notion with an  operational notion.
␈⊃The left hand side of  this equation is denotational; it  expresses a
␈⊃functional  relationship.  The   right  hand  side   is  operational,
␈⊃expressing   a   procedure   call.    This   denotational-operational
␈⊃distinction is appropriate  in a more  general context.  When  we are
␈⊃presented with someone's program and asked "what does it compute?" we
␈⊃usually begin our investigation operationally, discovering "what does
␈⊃it do?" 19.  Frequently, by tracing its execution, we can  discover a
␈⊃denotational description: E.g., "ah! it computes the square root".
␈⊃
␈⊃When  great  mother was  presented  it was  given  as  an operational
␈⊃exercise, with the primary intent of introducing the  LISP evaluation
␈⊃process  without  involving  complicated  names  and  concepts. Forms
␈⊃involving great mother  were evaluated perhaps  without understanding
␈⊃the denotation, but if asked  "what does great mother do?"  you would
␈⊃be  hard  pressed  to  given  a  comprehensible   purely  operational
␈⊃definition. However, you might have discovered the intended nature of
␈⊃great mother yourself; then  it would be relatively easy  to describe
␈⊃its (her) behavior. Indeed,  once the denotation of great  mother has
␈⊃been   discovered   questions   like   "What   is   the    value   of
␈⊃tgmoaf[(CAR (QUOTE (A . B)))]? "  are usually  answered by  using the
␈⊃denotation  of  tgmoaf:  "what is  the  value  of  car[(A . B)]?" The
␈⊃question  of  how one  gives  a convincing  argument  that  eval does
␈⊃faithfully represent LISP evaluation is the subject of [Gor 73].
␈⊃
␈⊃In discussing models  for LISP, we  will parallel our  development of
␈⊃interpreters for LISP since  each subset, tgmoaf, tgmoafr,  and eval,
␈⊃will also introduce new problems for our mathematical semantics.
␈⊃
␈⊃Our first LISP subset considers functions, compostion, and constants.
␈⊃Constants  will be  elements of  our domain  of  interpretation.  Our
␈⊃domain  will include  the S-expressions  since most  LISP expressions
␈⊃denote  S-exprs;  since  many  of  our  LISP  functions  are  partial
␈⊃functions, it is convenient to talk about the undefined value,  B. We
␈⊃wish to  extend our  partial functions  to be  total functions  on an
␈⊃extended  domain.  As  before (page ),  we shall  call  this extended
␈⊃domain S.
␈⊃
␈⊃Before  we   can  talk  very   precisely  about  the   properties  of
␈⊃mathematical functions denoted by  LISP functions, we must  give more
␈⊃
␈⊃________________
␈⊃ 19 Another common  manifestation of this "denotation"  phenomonon is
␈⊃the common programmer complaint: "It's easier to write your  own than
␈⊃to understand someone else's."
␈⊃.1                                       Review and Reflection     21
␈⊃
␈⊃
␈⊃careful  study to  the nature  of domains.   Our primitive  domain is
␈⊃<atom>.  Its  intuitive structure is  quite simple, basically  just a
␈⊃set  of atoms  or names  with no  inherent relationships  among them.
␈⊃Another primitive domain is Tr, the domain of truth values.
␈⊃
␈⊃The domain <sexpr> is more complex; it is a set of elements, but many
␈⊃elements  are  related. In  our  discussion of  <sexpr>  on  page  we
␈⊃made it clear that there is more than syntax involved.  We  could say
␈⊃that  for  s1 and  s2  in <sexpr>  the  essence of  "dotted  pair" is
␈⊃contained in the concept of the set-theoretic ordered  pair, <s1,s2>.
␈⊃Thus  the  "meaning"  of  the set  of  dotted  pairs  is  captured by
␈⊃Cartesian product, <sexpr> x <sexpr>.
␈⊃
␈⊃Let's continue the analysis of:
␈⊃              <sexpr> ::= <atom> | (<sexpr> . <sexpr>).
␈⊃
␈⊃We are trying to interpret  this BNF equation as a definition  of the
␈⊃domain <sexpr>. Reasonable interpretations of "::=" and "|" appear to
␈⊃be equality  and set-theoretic union,  respectively. This  results in
␈⊃the equation:
␈⊃               <sexpr> = <atom> ∪ <sexpr> x <sexpr> .
␈⊃
␈⊃This looks like  an algebraic equation, and  as such, may or  may not
␈⊃have solutions.   This particular "domain  equation" has  a solution:
␈⊃the S-exprs.
␈⊃
␈⊃There  is  a  natural  mapping of  BNF  equations  onto  such "domain
␈⊃equations",  and  the  solutions to  the  domain  equations  are sets
␈⊃satisfying the abstract essence of the BNF.
␈⊃
␈⊃The mapping  process is also  applicable to the  language constructs.
␈⊃Consider the BNF equations for a simple applicative subset of LISP:
␈⊃ <form> ::= <variable> | λ[[<variable>] <form> |  <variable [<form>]
␈⊃
␈⊃We would  like to describe  the denotations of  these equations  in a
␈⊃style similar  to that  used for <sexpr>'s.   The denotations  of the
␈⊃expressions, <form>, of  applications, <variable>[form>], and  of the
␈⊃variables, <variables>, are just constants of the language; call this
␈⊃domain C.
␈⊃
␈⊃Expressions  of  the  form  "λ[[<variable>] <form>]"  are  to  denote
␈⊃functions. First we consider the set of functions from C to  C. Write
␈⊃that set as C → C. Then our domain equation is expressed:
␈⊃
␈⊃                            C =   C→C ∪ C
␈⊃
␈⊃This  equation  has  no  interesting  solutions.  A  simple  counting
␈⊃argument will establish that unless the domain C is a single element,
␈⊃then the number of functions  in C → C is greater than the  number of
␈⊃22                                                                 .1
␈⊃
␈⊃
␈⊃elements in C.   This does not  say that there  are no models  of the
␈⊃λ-calculus.  It says is that our interpretation of "→" is too broad.
␈⊃
␈⊃What is needed is an interpretation of functionality which will allow
␈⊃a solution to  the above domain equation;  it should allow  a natural
␈⊃interpretation such that the properties which we expect  functions to
␈⊃possess are true in the model.  Scott gave one such interpretation of
␈⊃"→", defining  the class  of "continuous  functions".  This  class of
␈⊃functions is  restricted enough to  satisfy the domain  equation, but
␈⊃broad enough to act  as the denotations of procedures  in applicative
␈⊃programming languages.  We will use the notation "[D1 → D2]"  to mean
␈⊃"the set of continuous functions from domain D1 to domain D2".  It is
␈⊃the  continuous  functions  which  first  supplied  a  model  for the
␈⊃λ-calculus  and it  is these  functions which  supply a  basis  for a
␈⊃mathematical model of LISP.
␈⊃
␈⊃We can  assume that  the LISP  primitives denote  specific continuous
␈⊃functions.  For example,  the  mathematical counterpart  to  the LISP
␈⊃function car is the mapping car from S to S defined as follows:
␈⊃
␈⊃
␈⊃         car: [S → S]
␈⊃
␈⊃                   is B if t is atomic
␈⊃         car(t)    is t1 if t is (t1 . t2)
␈⊃                   is B if t is B.
␈⊃
␈⊃
␈⊃Similar strategy suffices to give denotations for the other primitive
␈⊃LISP functions and predicates. For example:
␈⊃
␈⊃
␈⊃         atom: [S → S]
␈⊃
␈⊃                   is f if t is not atomic.
␈⊃         atom(t)   is t if t is atomic.
␈⊃                   is B if t is B.
␈⊃
␈⊃
␈⊃Notice that these functions are strict: f(B) = B.
␈⊃
␈⊃Corresponding to  tgmoaf, we  will have a  function, Dtg,  which maps
␈⊃expressions  onto their  denotations.  Since  Dtg is  another mapping
␈⊃like  r,  we  will use  the  "("  and ")"  brackets  to  enclose LISP
␈⊃constructs.  We need to  introduce some notation for elements  of the
␈⊃sets <sexpr> and  <form>. Let s range  over <sexpr> and f  range over
␈⊃<form>, then we can write:
␈⊃
␈⊃                             Dtg(s) = s
␈⊃                      Dtg(car[f]) = car(Dtg(f))
␈⊃.1                                       Review and Reflection     23
␈⊃
␈⊃
␈⊃with similar entries for cdr,  cons, eq, and atom.  The  structure of
␈⊃this definition is very similar to that of tgmoaf.
␈⊃
␈⊃Now  we  continue to  the  next subset  of  LISP,  adding conditional
␈⊃expressions to  our discussion.  As we  noted on  page , a  degree of
␈⊃care  need  be  taken  when  we  attempt  to   interpret  conditional
␈⊃expressions  in  terms  of mappings.   We  can  simplify  the problem
␈⊃slightly: it is easy to  show that the conditional expression  can be
␈⊃formulated in  terms of the  simple if-expression:  if[p1;e1;e2].  We
␈⊃will  display a  denotation for  such if  expressions. It  will  be a
␈⊃mathematical function, and  therefore the evaluation order  will have
␈⊃been abstracted out 20.
␈⊃Let if denote if where:
␈⊃
␈⊃         if: [TrxSxS → S]
␈⊃
␈⊃                     is y if x is t
␈⊃         if(x,y,z)   is z, if x is f.
␈⊃                     is B, otherwise
␈⊃
␈⊃This  interpretation of  conditional expressions  is  appropriate for
␈⊃LISP;  other  interpretations  of  conditionals  are   possible.  For
␈⊃example:
␈⊃
␈⊃         if1: [TrxSxS → S]
␈⊃
␈⊃                       is y if x is t
␈⊃         if1(x,y,z)    is z, if x is f.
␈⊃                       is B if x is B and y ≠ z
␈⊃                       is y if x is B and y = z      21.
␈⊃
␈⊃Neither if nor if1 are strict mappings.
␈⊃
␈⊃To add if expressions to Dtg, yielding Dtgr we include:
␈⊃
␈⊃           Dtgr(if[f1 ; f2; f3]) = if(Dtgr(f1),Dtgr(f2),Dtgr(f3))
␈⊃
␈⊃The  next  consideration  is  the  denotational  description  of LISP
␈⊃identifiers.   Identifiers  name either  S-exprs  or  LISP functions.
␈⊃
␈⊃________________
␈⊃ 20 Recall  the comment  of Wadsworth (page 11).  Indeed, the  use of
␈⊃conditional expressions in the more abstract representations  of LISP
␈⊃functions frequently is  such that exactly one  of the pi's is  t and
␈⊃all the others are f.  Thus in this setting, the order  of evaluation
␈⊃of the  predicates is  useful for "efficiency"  but not  necessary to
␈⊃maintain the sense of the definition. See page .
␈⊃
␈⊃ 21 Basing  conditional expressions on  if1 would  give [car[A] → 1; 
␈⊃t → 1] value 1.
␈⊃24                                                                 .1
␈⊃
␈⊃
␈⊃Thus  an identifier  denotes  either an  object  on our  domain  S or
␈⊃denotes  a  function  object.   Let Fn  name  the  set  of continuous
␈⊃functions: Sn=0[Sn → S], and Id be <identifier>∪B.  We know  that the
␈⊃value  of  a  LISP  <identifier>  (page )  depends  on   the  current
␈⊃environment. Then we might characterize the set of environments, env,
␈⊃as:
␈⊃
␈⊃                           [Id → S ∪ Fn].
␈⊃
␈⊃That is,  an element of  env is a  continuous function which  maps an
␈⊃identifier  either  onto a  S-expr  or onto  an  n-ary  function from
␈⊃S-exprs to  S-exprs.  This  is the  essence of  the argument  used in
␈⊃introducing  assoc   (Section ).   Note  that   assoc[x;l] = l(x)  is
␈⊃another instance of a operational-denotational relationship.
␈⊃
␈⊃Given a  LISP identifier, x,  and a member  of env, say  the function
␈⊃{<x,2>,<y,4>},  then  the new  D  should map  x  onto 2.  This  is an
␈⊃intuitive way of  saying that D should  map a member  of <identifier>
␈⊃onto  a function.  This function  will map  a member  of env  onto an
␈⊃element  of  S.   Introducing  i as  a  meta-variable  to  range over
␈⊃<indentifier>, then for l ε env we have:
␈⊃                           D(i)(l) = l(i)
␈⊃
␈⊃The meaning or denotation of a identifier is a function;  whereas the
␈⊃value of an identifier is an element of S∪Fn.
␈⊃
␈⊃The  treatment  of  identifiers leads  directly  into  the denotional
␈⊃aspects  of function  application.  We  shall maintain  the parallels
␈⊃between evaluation and denotation, by giving De and  Da corresponding
␈⊃to eval and apply.  Let g be a member of <function> and f be a member
␈⊃of <form>, then for a given element of env, Da maps g onto an element
␈⊃of Fn, and De maps f onto an element of S.
␈⊃For example:              Da(car)(l) = car
␈⊃
␈⊃Similar equations  hold for  the other  LISP primitive  functions and
␈⊃predicates.  In general, then:
␈⊃
␈⊃               Da(f)(l) = l(f), where f ε <function>.
␈⊃
␈⊃To describe the evaluation of a function-call in LISP we must  add an
␈⊃equation to De:
␈⊃
␈⊃              De(f[s1, ...,sn])(l) = 
␈⊃Da(f)(l)(De(s1)(l), ...,De(sn)(l))
␈⊃
␈⊃We must also make consistent modifications to the previous clauses of
␈⊃Dtgr to account for environments.   That is, the value of  a constant
␈⊃is independent of the specific environment in which it is evaluated.
␈⊃                            De(s)(l) = s.
␈⊃.1                                       Review and Reflection     25
␈⊃
␈⊃
␈⊃We  must  also  extend  our  equations  to  account  for  conditional
␈⊃expressions.
␈⊃
␈⊃Before we get very far in applying functions to values we must give a
␈⊃mathematical  characterization  of  function  definitions.   In  this
␈⊃section we will handle λ-notation without free  variables, postponing
␈⊃more complex cases to Section .
␈⊃
␈⊃Assuming  the  only free  variables  in  x are  among  the  xi's, the
␈⊃denotation of λ[[x1; ...; xn] x] in a specified environment should be
␈⊃a function from Sn to S such that:
␈⊃
␈⊃              Da(λ[[v1; ... ;vn]s])(l) = 
␈⊃λ(x1, ..., xn)De(s)(l : <x1,v1>, ..., <xn,vn>)
␈⊃
␈⊃where λ is the LISP λ-notation and λ is its  mathematical counterpart
␈⊃and vi is  the denotational counterpart  of vi, and  (l : ... ) means
␈⊃the environmeent l augmented with the explicitly given pairs.
␈⊃
␈⊃That is, (l : <x1,v1>, ..., <xn,vn>) is a modification of l such that
␈⊃each vi is bound to the corresponding xi.
␈⊃That is:           (l : <x,v>) is: λ(v1) if v = B then B
␈⊃                                       else if v1 = B then B
␈⊃                                       else if v1 = x then v
␈⊃                                       else l(v1).
␈⊃
␈⊃where: if p then q else r  is  if(p,q,r).
␈⊃
␈⊃In more detail: λ(x1, ... ,xn)e(x1, ... ,xn) is a function f: Sn  → S
␈⊃such that:
␈⊃
␈⊃              is e(t1, ... ,tn) if m≥n and ∀i ti ≠ B. 22
␈⊃f(t1, ..., tm)
␈⊃              is B otherwise
␈⊃
␈⊃
␈⊃Analysis of our study will  show that one of the  larger difficulties
␈⊃was caused  by our  insistence on  dealing with  type-free languages.
␈⊃Self-application is one indication of this. We can show that imposing
␈⊃a  type  structure  on  our language  will  also  solve  many  of the
␈⊃questions  of non-termination.  In a  typed λ-calculus  an expression
␈⊃will always have a normal form ([Mor 68]). Computationally this means
␈⊃that  all  the  programs  will  terminate.   Also  models  for  typed
␈⊃λ-calculus are  much more  readily attained.   However the  type free
␈⊃calculus is a stronger system, and requiring all expressions  to have
␈⊃a consistent type structure  rules out several useful  constructs; in
␈⊃
␈⊃________________
␈⊃ 22 Note that  this equation allows the  LISP trick of  supplying too
␈⊃many arguments.
␈⊃26                                                                 .1
␈⊃
␈⊃
␈⊃particular,  the λ-calculus  counterpart to  the LISP  label operator
␈⊃cannot be consistently typed.
␈⊃
␈⊃From the practical side, a typed structure is also a  mixed blessing.
␈⊃LISP  programmers   frequently  miss   the  declarations   of  common
␈⊃programming languages.  Language delarations are a form of typing and
␈⊃can be quite helpful in pinpointing programming  errors; declarations
␈⊃can also be used by compilers to help produce optimized code. However
␈⊃those same programmers want to subvert the type  structure, typically
␈⊃in the name of efficiency  or expediency.  Also a type  structure can
␈⊃be a real nuisance when  trying to debug a program; it  is frequently
␈⊃desireable to examine and modify the representations of abstract data
␈⊃structures. Those kinds of operations imply the ability to ignore the
␈⊃type information.
␈⊃
␈⊃Logically,  the  next  addition  to  D  would  involve  recursion and
␈⊃function definitions: label  and "<=".  We  know that the  LISP label
␈⊃operator is similar to "<=", but label builds a temporary definition,
␈⊃while "<=" modifies the environment. Programming  language constructs
␈⊃which modify the environment are said to have side-effects and are an
␈⊃instance of what  is called a  imperative construct.  Since  our main
␈⊃interest  lies  in  the  programming  aspects,  we  will  pursue  the
␈⊃procedural aspects  and postpone the  mathematics.  The  next chapter
␈⊃introduces  the procedural  aspects of  imperative constructs  and in
␈⊃Section   we will  investigate some  of the  mathematical  aspects of
␈⊃"<=" and label.
␈⊃
␈⊃
␈⊃
␈⊃                              Problems
␈⊃
␈⊃
␈⊃I.  Recall  the  problem   on  page ,  dealing  with   the  following
␈⊃factorial algorithm:
␈⊃
␈⊃                  fact <= λ[[n] f[function[f]; n]]
␈⊃
␈⊃            f <= λ[[g;n][n=0 → 1; t → *[n; g[g; n-1]] ]]
␈⊃
␈⊃Rewrite f in terms a unary function t:
␈⊃
␈⊃      t <= λ[[x] function[λ[[n][n=0 → 1; t → *[n; x[n-1]] ]]]].
␈⊃
␈⊃Show that fact = t[fact].
␈⊃.1                                       Review and Reflection     27
␈⊃
␈⊃
␈⊃
␈⊃II.  The  language  described  by the  a  and  b  rules  doesn't look
␈⊃particularly  rich,  similar  in power  to  LISP  with  just function
␈⊃application  but without  conditional expressions.   That is  only an
␈⊃illusion.  Show   that  we  can   represent  a  simple   if  function
␈⊃if[p;then;otherwise].   Hint:   show  that   λ[[x;y] y]  is   a  good
␈⊃representation for f and λ[[x;y] x] is a good representation for t.
␈⊃28  BIBLIOGRPAHY                                                   1.
␈⊃
␈⊃
␈⊃                            BIBLIOGRAPHY
␈⊃
␈⊃
␈⊃
␈⊃
␈⊃The basic form of an entry consists of three items:
␈⊃
␈⊃     1. A short name which  is how the document is referenced  in the
␈⊃     text.
␈⊃
␈⊃     2. The full bibliographical reference.
␈⊃
␈⊃     3. A sequence of pages in the text which refer to this document.
␈⊃     If  the  document  is not  referenced  the  statement [ norefs ]
␈⊃     appears instead.
␈⊃
␈⊃[Alg 63]   8 [ norefs ]
␈⊃
␈⊃[Ama 72]   1 [ norefs ]
␈⊃
␈⊃[Boy 75]   3 [ norefs ]
␈⊃
␈⊃[Car 76]   3 [ norefs ]
␈⊃
␈⊃[Chu 41]   11 [ norefs ]
␈⊃
␈⊃[Dar 73]   3 [ norefs ]
␈⊃
␈⊃[Dav 76]   6 [ norefs ]
␈⊃
␈⊃[Gor 73]   20 [ norefs ]
␈⊃
␈⊃[Hew 76]   16 [ norefs ]
␈⊃
␈⊃[Hoa 69]   6 [ norefs ]
␈⊃
␈⊃[Lan 64]   17 [ norefs ]
␈⊃
␈⊃[Man 74]   3 [ norefs ]
␈⊃
␈⊃[McC 62]   9 [ norefs ]
␈⊃
␈⊃[McC 66]   8 [ norefs ]
␈⊃
␈⊃[Men 64]   5 [ 6 ]
␈⊃
␈⊃[Moor 75a] 8 [ norefs ]
␈⊃
␈⊃[Mor 55]   6 [ norefs ]
␈⊃1.                                                BIBLIOGRPAHY     29
␈⊃
␈⊃
␈⊃[Mor 68]   25 [ norefs ]
␈⊃
␈⊃[Pop 68a]  3 [ norefs ]
␈⊃
␈⊃[Pra 73]   6 [ norefs ]
␈⊃
␈⊃[Rog 67]   2 [ 3 ]
␈⊃
␈⊃[Sco 72]   5 [ norefs ]
␈⊃
␈⊃[Sta 74]   4 [ norefs ]
␈⊃
␈⊃[Sus 75]   16 [ norefs ]
␈⊃
␈⊃[Ten 76]   8 [ norefs ]
␈⊃
␈⊃[Wad 71]   18 [
␈⊃                                                         INDEX     31
␈⊃
␈⊃
␈⊃a-rule   16
␈⊃b-rule   15
␈⊃analytic syntax   9
␈⊃bound variable   14
␈⊃computed function   2
␈⊃concrete syntax   9
␈⊃denotational   12
␈⊃free variable   14
␈⊃operational   11
␈⊃partial application   4
␈⊃reduction rule   17
␈⊃referential transparency   16
␈⊃self-applicative   19
␈⊃